﻿using System;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using Persons;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Generic;
using System.Threading;

namespace PersonClient
{
    public class PConnection
    {
        private Socket _socket;
        private readonly List<IPEndPoint> _hosts;
        private int _activeHost = -1;

        public bool Connected { get; private set; }

        public PConnection()
        {
            _socket = null;
            _hosts = new List<IPEndPoint>();
            _activeHost = -1;
            Connected = false;
        }

        ~PConnection()
        {
            Disconnect();
        }

        public int AddHost(string hostName, int port)
        {
            var addedHost = 0;
            try
            {
                var ips = Dns.GetHostAddresses(hostName);
                if (ips != null)
                {
                    foreach (var ipe in ips.Select(ip => new IPEndPoint(ip, port)))
                    {
                        _hosts.Add(ipe);
                        addedHost++;
                    }
                }
            }
            catch (Exception e)
            {
                throw new Exception("Error while adding host", e);
            }
            return addedHost;
        }

        private void SelectHost()
        {
            if (_hosts != null && _hosts.Count > 0)
            {
                _activeHost = 0;
            }
        }

        private void ClearHosts()
        {
            _hosts.Clear();
            _activeHost = -1;
        }

        public bool Connect()
        {
            if (_socket == null || !_socket.Connected || !Connected)
            {
                _socket = null;
                SelectHost();
                if (_activeHost >= 0)
                { 
                    var ipe = _hosts[_activeHost];
                    _socket = new Socket(ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                    while (!_socket.Connected)
                    {
                        Thread.Sleep(100);
                        try
                        {
                            _socket.Connect(ipe);
                        }
                        catch (SocketException se)
                        {
                            if (se.ErrorCode == 10061)
                            {
                                continue;
                            }
                        }
                        catch (Exception e)
                        {
                            throw e;
                        }
                    }
                    if (_socket.Connected)
                    {
                        _socket.ReceiveBufferSize = 1024 * 2024;
                        _socket.ReceiveTimeout = 60 * 1000;
                        _socket.SendBufferSize = 102400;
                        _socket.Blocking = true;
                        _socket.DontFragment = false;
                        Connected = true;
                    }
                }
            }
            return Connected;
        }

        public void Disconnect()
        {
            if (_socket != null && _socket.Connected && Connected)
            {
                try
                {
                    _socket.Shutdown(SocketShutdown.Both);
                    _socket.Disconnect(false);
                    _socket.Close();
                }
                catch (Exception e) 
                {
                    throw new Exception("Error while disconnecting.", e);
                }
            }
            Connected = false;
            _socket = null;
            ClearHosts();
        }

        public bool Send(Person person)
        {
            if (_socket != null && _socket.Connected && Connected)
            {
                try
                {
                    var networkStream = new NetworkStream(_socket);
                    var bf = new BinaryFormatter();
                    bf.Serialize(networkStream, person);
                    networkStream.Close();
                    return true;
                }
                catch (Exception e)
                {
                    throw new Exception("Error while sending message.", e);
                }
            }
            return false;
        }
    }
}
